package com.uparlange.project.flextris
{
	import com.uparlange.project.flextris.util.RandomUtil;
	
	import flash.geom.Point;
	import flash.utils.describeType;
	import flash.utils.getDefinitionByName;
	
	import mx.core.IVisualElement;
	import mx.states.State;
	
	import spark.components.supportClasses.SkinnableComponent;

	/**
	 * Represents a piece
	 * 
	 * @author Ugo Parlange
	 */ 
	[Bindable]
	public class Piece extends SkinnableComponent
	{
		/**
		 * Skin part of piece part A
		 */
		[SkinPart(required="true")]
		public var parta : IVisualElement;
		
		/**
		 * Skin part of piece part B
		 */
		[SkinPart(required="true")]
		public var partb : IVisualElement;
		
		/**
		 * Skin part of piece part C
		 */
		[SkinPart(required="true")]
		public var partc : IVisualElement;
		
		/**
		 * Skin part of piece part D
		 */
		[SkinPart(required="true")]
		public var partd : IVisualElement;		
		
		/**
		 * Column index of the piece
		 */ 
		private var _columnIndex : Number = 0;
		
		/**
		 * Line index of the piece
		 */
		private var _lineIndex : Number = 0;
		
		/**
		 * Part width
		 */ 
		public var partWidth : int = 20;
		
		/**
		 * Part height
		 */ 
		public var partHeight : int = 20;
		
		/**
		 * Constructor
		 */ 
		public function Piece() {
			super();
		}
		
		/**
		 * Override attachSkin
		 */ 
		override protected function attachSkin() : void {
			super.attachSkin();
			// init state
			if(skin.states.length > 0) {
				var initState : State = skin.states[RandomUtil.getRandInRange(skin.states.length)] as State;
				setCurrentSkinState(initState.name);
			}
		}		
		
		/**
		 * Override partAdded
		 */ 
		override protected function partAdded(partName : String, instance : Object) : void {
			super.partAdded(partName, instance);
			// init position
			if(instance == parta) {
				refresh();
			}
		}
		
		/**
		 * Ovrerride getCurrentSkinState
		 */ 
		override protected function getCurrentSkinState() : String {
			return skin.currentState;
		}
		
		/**
		 * Gets all parts of the piece
		 * 
		 * @return Array, the list of parts
		 */
		public function getParts() : Array {
			// part a
			var partaPiecePart : PiecePart = new PiecePart();
			partaPiecePart.position = new Point((x + parta.x) / parta.width, (y + parta.y) / parta.height);
			partaPiecePart.visualElement = parta;
			// part b
			var partbPiecePart : PiecePart = new PiecePart();
			partbPiecePart.position = new Point((x + partb.x) / partb.width, (y + partb.y) / partb.height);
			partbPiecePart.visualElement = partb;
			// part c
			var partcPiecePart : PiecePart = new PiecePart();
			partcPiecePart.position = new Point((x + partc.x) / partc.width, (y + partc.y) / partc.height);
			partcPiecePart.visualElement = partc;
			// part d
			var partdPiecePart : PiecePart = new PiecePart();
			partdPiecePart.position = new Point((x + partd.x) / partd.width, (y + partd.y) / partd.height);
			partdPiecePart.visualElement = partd;
			// return all parts
			return [partaPiecePart, partbPiecePart, partcPiecePart, partdPiecePart];
		}
		
		/**
		 * Check if the piece can move bottom
		 * 
		 * @param aMatrice Matrice, the matrice's constraint
		 * 
		 * @return Boolean, true if can move bottom
		 */ 
		public function canMoveBottom(aMatrice : Matrice) : Boolean {
			var points : Array = getPoints();
			for each(var point : Point in points) {
				point.y += 1;
			}
			return aMatrice.hasSpace(points);
		}
		
		/**
		 * Move the piece bottom
		 * 
		 * @param aMatrice Matrice, the matrice's constraint
		 * @param aPlaySound Boolean, to know if have to play a sound at moving
		 */ 
		public function moveBottom(aMatrice : Matrice, aPlaySound : Boolean = false) : void {
			if(canMoveBottom(aMatrice)) {
				// play sound
				if(aPlaySound) {
					JukeBox.getInstance().playMoveBlock();
				}
				// move piece
				lineIndex += 1;
			}
		}
		
		/**
		 * Move the piece right
		 * 
		 * @param aMatrice Matrice, the matrice's constraint
		 */  
		public function moveRight(aMatrice : Matrice) : void {
			if(canMoveRight(aMatrice)) {
				// play sound
				JukeBox.getInstance().playMoveBlock();
				// move piece
				columnIndex += 1;
			}
		}
		
		/**
		 * Move the piece left
		 * 
		 * @param aMatrice Matrice, the matrice's constraint
		 */ 
		public function moveLeft(aMatrice : Matrice) : void {
			if(canMoveLeft(aMatrice)) {
				// play sound
				JukeBox.getInstance().playMoveBlock();
				// move piece
				columnIndex -= 1;
			}
		}
		
		/**
		 * Gets a ghost piece
		 * 
		 * @param aMatrice Matrice, the matrice's constraint
		 * 
		 * @return Piece, the ghost piece
		 */ 
		public function getGhostPiece(aMatrice : Matrice) : Piece {
			var desc : XML = describeType(this);
			var p : Class = getDefinitionByName(desc.@name.toString()) as Class;
			var piece : Piece = new p() as Piece;
			piece.columnIndex = columnIndex;
			piece.lineIndex = lineIndex;
			piece.alpha = 0.3;
			return piece;
		} 
		
		/**
		 * Rotate a piece
		 * 
		 * @param aMatrice Matrice, the matrice's constraint
		 * @param aClockwise Boolean, to know if making a clockwise or a counterclockwise rotation
		 */ 
		public function rotate(aMatrice : Matrice, aClockwise : Boolean = true) : void {
			if(canRotate(aMatrice, aClockwise)) {
				// play sound
				JukeBox.getInstance().playRotateBlock();
				// rotate piece
				setCurrentSkinState(getNextState(aClockwise));
			}
		}
		
		/**
		 * Called to update position
		 */ 
		public function refresh(aWidth:Number=-1, aHeight:Number=-1) : void {
			if(aWidth != -1) {
				if(parta) {
					parta.width = aWidth;
				}
				if(partb) {
					partb.width = aWidth;
				}
				if(partc) {
					partc.width = aWidth;
				}
				if(partd) {
					partd.width = aWidth;
				}
			}
			if(aHeight != -1) {
				if(parta) {
					parta.width = aWidth;
				}
				if(partb) {
					partb.width = aWidth;
				}
				if(partc) {
					partc.width = aWidth;
				}
				if(partd) {
					partd.width = aWidth;
				}
			}
			initUpdateXposition();
			initUpdateYposition();
		}
		
		/**
		 * Called to set skin current state
		 * 
		 * @param aState String, the state to set
		 */ 
		public function setCurrentSkinState(aState : String) : void {
			skin.setCurrentState(aState);
		}
		
		/**
		 * Gets point's part of the piece
		 * 
		 * @return Array, the list of points
		 */ 
		private function getPoints() : Array {
			var points : Array = new Array();
			for each(var piecePart : PiecePart in getParts()) {
				points.push(piecePart.position);
			}
			return points;
		}
		
		/**
		 * Gets next rotation state
		 * 
		 * @param aClockwise Boolean, to know if making a clockwise or a counterclockwise rotation
		 * 
		 * @return String, the next rotation state
		 */ 
		private function getNextState(aClockwise : Boolean = true) : String {
			var nbStates : int = skin.states.length;
			// if no states, return null
			if(nbStates == 0) {
				return null;
			}
			// otherwise, get next direction
			var directionIndex : int;
			var state : State;
			for(var i : int = 0; i < nbStates; i++) {
				state = skin.states[i] as State;
				if(state.name == getCurrentSkinState()) {
					directionIndex = i;
					break;
				}
			}
			if(aClockwise) {
				directionIndex++;
			} else {
				directionIndex--;
			}
			// if directionIndex < 0, next direction = last direction
			if(directionIndex < 0) {
				directionIndex = nbStates - 1;
			// if directionIndex >= nbDirections, next direction = first direction
			} else if(directionIndex >= nbStates) {
				directionIndex = 0;
			}
			// return next direction
			return (skin.states[directionIndex] as State).name;
		}	
		
		/**
		 * Check if the piece can rotate
		 * 
		 * @param aMatrice Matrice, the matrice's constraint
		 * @param aClockwise Boolean, to know if making a clockwise or a counterclockwise rotation
		 * 
		 * @return Boolean, true if can rotate
		 */ 
		private function canRotate(aMatrice : Matrice, aClockwise : Boolean = true) : Boolean {
			// if direction not setted, no need rotation
			// it must be only OPiece case
			if(skin.states.length == 0) {
				return false;
			}
			// otherwise, check if can rotate
			var css : String = getCurrentSkinState();
			var ncss : String = getNextState(aClockwise);
			setCurrentSkinState(ncss);
			var points : Array = getPoints();
			var cr : Boolean = aMatrice.hasSpace(points);
			setCurrentSkinState(css);
			return cr;
		}	
		
		/**
		 * Check if the piece can move right
		 * 
		 * @param aMatrice Matrice, the matrice's constraint
		 * 
		 * @return Boolean, true if can move right
		 */ 
		private function canMoveRight(aMatrice : Matrice) : Boolean {
			var points : Array = getPoints();
			for each(var point : Point in points) {
				point.x += 1;
			}
			return aMatrice.hasSpace(points);
		}
		
		/**
		 * Check if the piece can move left
		 * 
		 * @param aMatrice Matrice, the matrice's constraint
		 * 
		 * @return Boolean, true if can move left
		 */ 
		private function canMoveLeft(aMatrice : Matrice) : Boolean {
			var points : Array = getPoints();
			for each(var point : Point in points) {
				point.x -= 1;
			}
			return aMatrice.hasSpace(points);
		}
		
		/**
		 * Called to init/update x position of piece
		 */ 
		private function initUpdateXposition() : void {
			if(parta) {
				x = columnIndex * parta.width;
			}
		}
		
		/**
		 * Called to init/update x position of piece
		 */ 
		private function initUpdateYposition() : void {
			if(parta) {
				y = lineIndex * parta.height;
			}
		}
		
		/**
		 * Getter / Setter lineIndex
		 */ 
		public function set lineIndex(aValue : int) : void {
			_lineIndex = aValue;
			initUpdateYposition();
		}
		public function get lineIndex() : int {
			return _lineIndex;
		}
		
		/**
		 * Getter / Setter columnIndex
		 */ 
		public function set columnIndex(aValue : int) : void {
			_columnIndex = aValue;
			initUpdateXposition();
		}
		public function get columnIndex() : int {
			return _columnIndex;
		}
	}
}